﻿#define _CRT_SECURE_NO_WARNINGS


//命名空间namespace

//c的问题：命名冲突
//1.定义的变量跟库冲突
//2.不同文件下的变量互相之间冲突
//解决方案：用命名空间域进行隔离
//同一个域中不可以定义相同变量，不同域可以定义相同变量

//域：类域、命名空间域、局部域、全局域
//默认访问优先级：局部域 > 全局域 > 展开（暴露到全局）的/指定访问的命名空间域
//默认不访问命名空间域
//展开：编译时去命名空间域中搜索

using namespace std;//含义：展开标准库的命名空间
//直接展开会有风险，定义变量跟库重名则会报错，故不推荐轻易使用，建议项目里面不要去展开
//建议项目中指定访问
//要使用标准库的cout等，一般要在前面加上std::cout
//使用using namespace std;就可以不用加，就算加了也不会报错
//也可以把常用的指定展开，如：
//using std::cout;
//using std::endl;

//:: - 域作用限定符，访问全局域
//空间名+:: - 指定访问命名空间域

//1.命名空间中可以定义变量、函数、类型
// 
//2.命名空间之间可以嵌套
//例如：
//namespace N1
//{
//    int a = 0;
//    int b;
//    int Add(int left, int right)
//    {
//        return left + right;
//    }
//
//    namespace N2
//    {
//        int a = 1;
//        int c;
//        int d;
//        int Sub(int left, int right)
//        {
//            return left - right;
//        }
//    }
//}
//int main()
//{
//    printf("%d\n", N1::a);
//    printf("%d\n", N1::N2::a);
//    printf("%d\n", N1::Add(1, 2));
//    return 0;
//}
//3.多个同名的命名空间会被合并。不同空间中有相同变量名会报错。

//#include<iostream.h>  // 很老的编译器 VC6.0  没有命名空间
//有命名空间后优化为：
#include<iostream>



//C++输入输出

//std::cout << "hello world" << std::endl;
// << - 流插入运算符
//cout - 流提取运算符（自动识别类型）
//cin - 流输入运算符（自动识别类型）





//缺省参数（默认参数)

//void Func(int a = 0)	//a就是缺省参数，0就是缺省值
//{
//	cout << a << endl;
//}
//
//int main()
//{
//	Func();     // 没有传参时，使用参数的默认值
//	Func(10);   // 传参时，使用指定的实参
//
//	return 0;
//}

//分类

// 全缺省 - 所有参数都给了缺省值。传参必须要从左到右依次传参，且不可跳跃
//void Func(int a = 10, int b = 20, int c = 30)
//{
//    cout << "a = " << a << endl;
//    cout << "b = " << b << endl;
//    cout << "c = " << c << endl << endl;
//}
//
//int main()
//{
//    Func();
//    Func(1);
//    Func(1,2);
//    Func(1,2,3);
//
//    return 0;
//}

// 半缺省 - 缺省部分参数。缺省必须要从右往左缺省
//void Func(int a, int b = 20, int c = 30)
//{
//    cout << "a = " << a << endl;
//    cout << "b = " << b << endl;
//    cout << "c = " << c << endl << endl;
//}
//
//int main()
//{
//    //Func();		//不可行
//    Func(1);
//    Func(1,2);
//    Func(1,2,3);
//
//    return 0;
//}
//eg：
//栈的初始化
//struct Stack
//{
//	int* a;
//	int top;
//	int capacity;
//};
//
//void StackInit(struct Stack* pst, int defaultCapacity = 4)//初始给空间大小预设为4，既开辟空间又避免浪费
//{
//	pst->a = (int*)malloc(sizeof(int) * defaultCapacity);
//	if (pst->a == NULL)
//	{
//		perror("malloc fail");
//		return;
//	}
//
//	pst->top = 0;
//	pst->capacity = defaultCapacity;
//}
// //其他写法 依然不如缺省参数的方式灵活
////#define DEFAULT_CAPACITY 100
////void StackInit(struct Stack* pst)
////{
////	pst->a = (int*)malloc(sizeof(int) * DEFAULT_CAPACITY);
////	if (pst->a == NULL)
////	{
////		perror("malloc fail");
////		return;
////	}
////
////	pst->top = 0;
////	pst->capacity = DEFAULT_CAPACITY;
////}
//int main()
//{
//	struct Stack st1;
//	StackInit(&st1, 100);
//	// 插入100个数据
//
//	struct Stack st2;
//	//StackInit(&st2); -> StackInit(&st2, 4);
//	// 不知道要插入多少数据
//
//	return 0;
//}

//[ps]
// 1.
//缺省参数不能在函数声明和定义中同时出现，避免缺省值不一致。
//缺省值在函数声明时赋值，不可在定义时赋值。
//2.缺省值必须是常量或全局变量




//函数重载（同一个函数名有多个意思）

//C++允许在同一作用域中声明几个功能类似的同名函数，这
//些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同，常用来处理实现功能类似数据类型
//不同的问题。
//[ps]与两函数的返回类型相同与否无关。原因：若有关，因在编译阶段调用函数时会通过匹配参数而不会匹配返回值，则会引起歧义。

// 1、参数类型不同
// eg：
//int Add(int left, int right)
//{
//	cout << "int Add(int left, int right)" << endl;
//
//	return left + right;
//}
//
//double Add(double left, double right)
//{
//	cout << "double Add(double left, double right)" << endl;
//
//	return left + right;
//}
//
//int main()
//{
//	cout << Add(1, 2) << endl;
//	cout << Add(1.1, 2.2) << endl;
//
//	return 0;
//}

// 2、参数个数不同

// 3、参数类型顺序不同

//[补]
//void f()
//{
//	cout << "f()" << endl;
//}
//
//void f(int a = 0)
//{
//	cout << "f(int a)" << endl;
//}
//
//int main()
//{
//	f(); // “f”: 对重载函数的调用不明确
//
//	return 0;
//}
// Q1、两函数是否构成重载？ -- 构成
// Q2、运行问题？无参数传时调用存在歧义（不明确究竟调用哪一个）

//为什么c不支持重载，而cpp支持？cpp是怎样支持的？
// 1.编译链接过程
// 预处理：头文件展开、宏替换、条件编译（if/else去掉不符条件的多余部分）、去掉注释...基于test.c生成了一个新文件test.i
// 编译：检查语法，生成汇编代码（是一种符号型的语言代码，cpu读不懂）。基于test.i生成了test.s文件
// 汇编：将汇编代码转换为二进制机器码（cpu可读）。基于test.s生成了test.o文件
// 链接：生成可执行程序 - xxx.exe / a.out 。在生成可执行程序的过程中，所有.c文件会交互
// [补]
// 函数被汇编后会生成一个相应地址。编译阶段只有函数声明，没有函数主体，故编译阶段拿不到这个地址。
// 声明相当于一个承诺，
// 链接相当于兑现承诺（找到定义）。.o文件中有符号表，存了函数的地址，不同文件的函数在链接过程中基于此交互。链接成功相当于兑现成功，失败相当于兑现失败。
// 编译 - 声明：承诺
// 编译 - 定义：直接兑现承诺
// 链接 - 找到定义：兑现承诺
// 2.函数名修饰规则











//引用（取别名） - 解决指针的复杂问题




//eg：
//int main()
//{
//	int a = 0;
//	int& b = a;	//b就是a的别名
//	int& c = b;
//	int& d = a;
// // 一个变量可以有多个引用
//	//int& e;	//不可用
// 引用在定义时必须初始化，且一旦引用一个实体，再不能引用其他实体
//
//	//abcd都是同一块空间
//	cout << &a << endl;
//	cout << &b << endl;
//	cout << &c << endl;
//	cout << &d << endl;
//
//	b++;	//b++，a也++
//	d++;
//
//	int x = 11;
//	//这里，
//	// d变成x的引用(别名)呢？
//	// 还是x赋值给d？
//	d = x;	//x的值赋值给d
//
//	return 0;
//}

//void Swap(int& a, int& b)	//形参是实参的别名，可以像传地址解引用一样实现值的交换
//{
//	int tmp = a;
//	a = b;
//	b = tmp;
//}

//void Swap(int*& a, int*& b)	//指针的别名，交换指针
//{
//	int* tmp = a;
//	a = b;
//	b = tmp;
//}

//typedef struct ListNode
//{
//	int val;
//	struct ListNode* next;
//}LTNode;
//
////void ListPushBack(struct ListNode** pphead, int x)	//链表尾插，传二级指针
////void ListPushBack(LTNode*& phead, int x)		//传别名

//typedef struct ListNode
//{
//	int val;
//	struct ListNode* next;
//}LTNode, * PLTNode;	//struct ListNode * PLTNode是struct ListNode地址的引用
//void ListPushBack(PLTNode& phead, int x)
//{
//	// ...
//	// phead = newnode;	//phead是别名，改变phead即可改变被代替的变量
//	// ...
//}

//[ps]二级指针和引用谁好？ - 都可以，重要的是按需使用

//应用
// 1、引用做参数  （输出型参数）
//减少拷贝，提高效率）（大对象/深拷贝类对象）

//void Swap(int& left, int& right)
//{
//	int temp = left;
//	left = right;
//	right = temp;
//}
// 
//// int* preorderTraversal(struct TreeNode* root, int* returnSize)  -- c
//// int* preorderTraversal(struct TreeNode* root, int& returnSize)  -- cpp
// 
//typedef struct ListNode
//{
//	int val;
//	struct ListNode* next; // c / cpp
//	//ListNode* next;          // cpp
//}*PNode;

// 2、引用做返回值  （修改返回值+获取返回值）
//（减少拷贝提高效率）（大对象/深拷贝类对象）
//函数栈帧生成一个临时变量来存返回值，这个变量出栈帧即销毁
//引用作返回值，就不会再生成临时变量了。

// 传值返回
//int Count()
//{
//	static int n = 0;
//	n++;
//
//	// ...
//	return n;
//}
//
//int main()
//{
//	int ret = Count();
//
//	return 0;
//}

// 传引用返回
//int& Count()
//{
//	static int n = 0;
//	n++;
//	// ...
//	return n;
//}
//
//int main()
//{
//	int ret = Count();	//ret值是不确定的
//			//如果函数结束，栈帧销毁，没有清理，ret的结果就侥幸正确，反之则为随机值
// 
//
//	return 0;
//}
// 
// 
//小结：返回局部变量（出栈帧可能被清理）的引用非常危险。
//		静态变量不会因栈帧销毁而被清理，返回静态变量结果始终是确定。
//		故，最好在引用的是静态变量时才让引用做返回值

//[tip]
// 1、基本任何场景都可以用引用传参
// 2、谨慎用引用做返回值
//	  出了函数作用域，对象不在了就不可以用引用返回，对象还在才可以
//

//[拓展]
//struct SeqList
//{
//	int a[100];
//	size_t size;
//};
//
//int SLGet(SeqList* ps, int pos)
//{
//	assert(pos < 100 && pos >= 0);
//
//	return ps->a[pos];
//}
//
//void SLModify(SeqList* ps, int pos, int x)
//{
//	assert(pos < 100 && pos >= 0);
//
//	ps->a[pos] = x;
//}
//
//int& SLAt(SeqList& s, int pos)
//{
//	assert(pos < 100 && pos >= 0);
//
//	return s.a[pos];
//}
//
//int main()
//{
//	//int& ret = Count(10);
//	//cout << ret << endl;
//
//	////printf("sssssssssssssssssssssssss\n");
//	//rand();
//	//cout << ret << endl;
//
//	SeqList s;
//	SLModify(&s, 0, 1);
//	cout << SLGet(&s, 0) << endl;
//
//	// 对第0个位的值+5
//	int ret1 = SLGet(&s, 0);
//	SLModify(&s, 0, ret1+5);
//
//	SLAt(s, 0) = 1;
//	cout << SLAt(s, 0) << endl;
//	SLAt(s, 0) += 5;
//
//	return 0;
//}



//int main()
//{
//	// 不可以
//	// 引用过程中，权限不能放大
//	const int a = 0;
//	//int& b = a;
//
//	// 可以，c拷贝给d，没有放大权限，因为d的改变不影响c
//	const int c = 0;
//	int d = c;
//
//	// 不可以
//	// 引用过程中，权限可以平移或者缩小
//	int x = 0;
//	int& y = x;
//	const int& z = x;
//	++x;
//	//++z;
//
//	const int& m = 10;
//
//	double dd = 1.11;
//	int ii = dd;
//
//	const int& rii = dd;
//
//	return 0;
//}


//int func1()
//{
//	static int x = 0;
//	return x;
//}
//
//int& func2()
//{
//	static int x = 0;
//	return x;
//}
//
//int main()
//{
//	// int& ret1 = func1();  // 权限放大
//	//const int& ret1 = func1(); // 权限平移
//	// int ret1 = func1();  // 拷贝
//
//	int& ret2 = func2();		// 权限平移
//	const int& rret2 = func2();  // 权限缩小
//
//	return 0;
//}

//int main()
//{
//	int i = 1;
//	double j = 1.1;
//	if (j > i)
//	{
//		cout << "xxxxx" << endl;
//	}
//	
//	//运算符两侧类型不同，发生类型提升，i从int变成double
//	//而提升的过程中会生成一个8字节的临时变量，这个double类型的临时变量去和j比较
//	return 0;
//}

//与指针的区别（面试常考点）

//int main()
//{
//	int a = 10;
//
//	// 语法层面：不开空间，是对a取别名
//	int& ra = a;
//	ra = 20;
//
//	// 语法层面：开空间，存储a的地址
//	int* pa = &a;
//	*pa = 30;
//
//	//底层汇编指令实现的角度看，引用是类似指针的方式实现的
// 
//	return 0;
//}

//1. 引用概念上定义一个变量的别名，指针存储一个变量地址。
//
//2. 引用在定义时必须初始化，指针没有要求
//
//3. 引用在初始化时引用一个实体后，就不能再引用其他实体，而指针可以在任何时候指向任何
//一个同类型实体
//
//4. 没有NULL引用，但有NULL指针
//
//5. 在sizeof中含义不同：引用结果为引用类型的大小，但指针始终是地址空间所占字节个数(32
//
//	位平台下占4个字节)
//	6. 引用自加即引用的实体增加1，指针自加即指针向后偏移一个类型的大小
//7. 有多级指针，但是没有多级引用
//
//8. 访问实体方式不同，指针需要显式解引用，引用编译器自己处理
//
//9. 引用比指针使用起来相对更安全






//auto关键字


//#include<map>
//#include<string>
//#include<vector>

//int main()
//{
//	int a = 0;
//	int b = a;
//	auto c = a; // 根据右边的表达式自动推导c的类型
//	auto d = 1 + 1.11; // 根据右边的表达式自动推导d的类型
//	cout << typeid(c).name() << endl;
//	cout << typeid(d).name() << endl;
//
//	vector<int> v;
//
//	// 类型很长
//	//vector<int>::iterator it = v.begin();
//	// 等价于
//	auto it = v.begin();
//
//	std::map<std::string, std::string> dict;
//	//std::map<std::string, std::string>::iterator dit = dict.begin();
//	// 等价于
//	auto dit = dict.begin();
//
//	return 0;
//}

//void f(int)
//{
//	cout << "f(int)" << endl;
//}
//
//void f(int*)
//{
//	cout << "f(int*)" << endl;
//}
//
//int main()
//{
//	f(0);
//	f(NULL);
//	f(nullptr);		//cpp中空指针用nullptr
//}




//基于范围的for循环(C++11)

//int main()
//{
//	int arr[] = { 1, 2, 3, 4, 5 };
//
//	for (int i = 0; i < sizeof(arr) / sizeof(int); ++i)
//		arr[i] *= 2;
//
//	for (int* p = arr; p < arr + sizeof(arr) / sizeof(arr[0]); ++p)
//		cout << *p << " ";
//	cout << endl;
//
//	// 适用于数组
//	// 范围for 语法糖
//	// 依次取数组中数据赋值给e
//	// 自动迭代，自动判断结束
//	// for (int x : arr)
//	for (auto x : arr)
//	{
//		cout << x << " ";
//	}
//	cout << endl;
//
//	// 修改数据
//	for (auto& e : arr)
//	{
//		e *= 2;
//	}
//
//	for (auto e : arr)
//	{
//		cout << e << " ";
//	}
//	cout << endl;
//
//	return 0;
//}

//使用条件
//1. for循环迭代的范围必须是确定的
//对于数组而言，就是数组中第一个元素和最后一个元素的范围；对于类而言，应该提供
//
//begin和end的方法，begin和end就是for循环迭代的范围。
//注意：以下代码就有问题，因为for的范围不确定
//void TestFor(int array[])
//{
//	for (auto& e : array)
//		cout << e << endl;
//}
//2. 迭代的对象要实现++和 == 的操作。








//内联函数

//引例：
// 宏函数 
// 优点-- 不需要建立栈帧，提高调用效率
// 缺点-- 复杂，容易出错、可读性差、不能调试
//int Add(int x, int y)
//{
//	return (x + y) * 10;
//}
////#define Add(int x, int y) return (x+y);
////#define Add(x, y) x+y
////#define Add(x, y) (x+y)
//#define Add(x, y) ((x)+(y))
//
//int main()
//{
//	for (int i = 0; i < 10000; i++)
//	{
//		cout << Add(i, i + 1) << endl;
//	}
//
//	if (Add(10, 20))
//	{
//
//	}
//
//	Add(10, 20) * 20;
//
//	int a = 1, b = 2;
//	Add(a | b, a & b); // (a | b + a & b)
//
//	return 0;
//}


inline int Add(int x, int y)
{
	return (x + y) * 10;
}
//内联函数
// 适用于短小的频繁调用的函数
// inline对于编译器仅仅只是一个建议，最终是否成为inline，编译器自己决定
// 像类似函数就加了inline也会被否决掉
// 1、比较长的函数
// 2、递归函数
// 默认debug模式下，inline不会起作用，否则不方便调试了
//声明和定义不能分离，故一般直接定义在.h文件中